/**
 * Copyright 2019 吉鼎科技.

 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cn.easyplatform.contexts;

import cn.easyplatform.FieldNotFoundException;
import cn.easyplatform.ScriptEvalExitException;
import cn.easyplatform.ScriptRuntimeException;
import cn.easyplatform.dao.BizDao;
import cn.easyplatform.dos.FieldDo;
import cn.easyplatform.dos.Record;
import cn.easyplatform.entities.beans.table.TableBean;
import cn.easyplatform.entities.beans.task.Variable;
import cn.easyplatform.entities.helper.EventLogic;
import cn.easyplatform.interceptor.CommandContext;
import cn.easyplatform.lang.Lang;
import cn.easyplatform.lang.Strings;
import cn.easyplatform.support.scripting.CompliableScriptEngine;
import cn.easyplatform.support.scripting.ScriptEngineFactory;
import cn.easyplatform.type.ScopeType;
import cn.easyplatform.util.RuntimeUtils;

import java.io.Serializable;
import java.util.*;


/**
 * @author <a href="mailto:davidchen@epclouds.com">littleDog</a> <br/>
 * @since 2.0.0 <br/>
 */
public class SwiftContext implements Serializable {

    /**
     *
     */
    private static final long serialVersionUID = 1L;

    /**
     * 类型,例如MT535
     */
    private String id;

    // 系统变量、自定义变量
    private Map<String, Object> systemVariables = new HashMap<String, Object>();

    private Map<String, FieldDo> userVariables;// = new HashMap<String, FieldDo>();// 公共变量
    //
    private Map<String, List<RecordContext>> records = new LinkedHashMap<String, List<RecordContext>>();
    //
    private boolean isEditable;
    //
    private RecordNode root;
    // 主表id
    private TableBean table;

    /**
     * 新增列表记录时自动给一个主键值
     */
    private int genKeyIndex = 0;

    /**
     * @param id
     */
    SwiftContext(String id, TableBean table, boolean isEditable,
                 Map<String, Object> systemVariables,
                 Map<String, FieldDo> taskVariables) {
        this.id = id;
        this.isEditable = isEditable;
        this.systemVariables.putAll(systemVariables);
        this.systemVariables.remove("837");// 移除确认逻辑
        this.table = table;
        userVariables = taskVariables;
/*        for (FieldDo fd : taskVariables.values()) {
            if (fd.getScope() == ScopeType.PROTECTED
                    || fd.getScope() == ScopeType.PUBLIC)
                if (fd.getShareModel() == Variable.CLONE_VALUE)
                    userVariables.put(fd.getName(), fd.clone());
                else
                    userVariables.put(fd.getName(), fd);
        }*/
    }

    /**
     * @return
     */
    public int getSize(String tagName, Object[] keys) {
        RecordNode node = getNode(tagName, keys);
        if (node != null)
            return node.children == null ? 0 : node.children.size();
        return 0;
    }

    /**
     * @param tagName
     * @param keys
     * @return
     */
    private RecordNode getNode(String tagName, Object[] keys) {
        if (Strings.isBlank(tagName))
            return root;
        return getNode(root.children, tagName, keys);
    }

    /**
     * @param children
     * @param tagName
     * @param keys
     * @return
     */
    private RecordNode getNode(Collection<RecordNode> children, String tagName,
                               Object[] keys) {
        for (RecordNode node : children) {
            if (node.name.equals(tagName) && Lang.equals(node.keys, keys))
                return node;
        }
        RecordNode result = null;
        for (RecordNode node : children) {
            if (result == null && !node.isLeaf())
                result = getNode(node.children, tagName, keys);
        }
        return result;
    }

    /**
     * @return the id
     */
    public String getId() {
        return id;
    }

    /**
     * @param isEditable
     */
    public void setEditable(boolean isEditable) {
        this.isEditable = isEditable;
    }

    /**
     * @param tagName
     * @param keys
     * @return
     */
    public RecordContext get(String tagName, Object[] keys) {
        if (Strings.isBlank(tagName)) {
            return records.get("").get(0);
        } else {
            List<RecordContext> list = records.get(tagName);
            for (RecordContext rc : list) {
                if (Lang.equals(rc.getKeyValues(), keys))
                    return rc;
            }
        }
        return null;
    }

    /**
     *
     */
    public void clear() {
        records.clear();
        if (root != null) {
            if (!root.isLeaf())
                root.children.clear();
        }
    }

    /**
     * @return
     */
    public RecordContext getRoot() {
        return records.get("").get(0);
    }

    /**
     * @param tagName
     * @return
     */
    public Collection<RecordContext> get(String tagName) {
        return records.get(tagName);
    }

    /**
     * @param parentName
     * @param parentKeys
     * @param name
     * @param rc
     * @param isGenKey
     */
    public void appendRecord(String parentName, Object[] parentKeys,
                             String name, RecordContext rc, boolean isGenKey) {
        if (isGenKey) {
            genKeyIndex--;
            rc.keyValues = new Object[1];
            rc.keyValues[0] = genKeyIndex + "pp";
        } else if (rc.keyValues == null) {
            rc.keyValues = new Object[rc.record.getKey().size()];
            for (int i = 0; i < rc.record.getKey().size(); i++) {
                rc.keyValues[i] = rc.record.get(rc.record.getKey().get(i))
                        .getValue();
            }
        }
        List<RecordContext> rcl = records.get(name);
        if (rcl == null) {
            rcl = new ArrayList<RecordContext>();
            records.put(name, rcl);
        }
        rcl.add(rc);
        if (Strings.isBlank(name)) {
            root = new RecordNode("", null);
        } else {
            RecordNode parent = getNode(parentName, parentKeys);
            parent.appendChild(new RecordNode(name, rc.keyValues));
        }
    }

    /**
     * @param record
     * @return
     */
    public RecordContext createRecord(Record record) {
        return new RecordContext(record, systemVariables, userVariables);
    }

    /**
     * @param tagName
     * @param keys
     */
    public void remove(String tagName, Object[] keys) {
        RecordContext rc = get(tagName, keys);
        RecordNode node = getNode(tagName, keys);
        node.parent.removeChild(node);
        if (rc.getParameterAsChar("814") == 'C') {
            records.get(tagName).remove(rc);
        } else {
            rc.setParameter("814", "D");
            rc.setParameter("815", Boolean.TRUE);
            if (!node.isLeaf())
                removeNodes(node.children);
        }
    }

    /**
     * @param children
     */
    private void removeNodes(Collection<RecordNode> children) {
        for (RecordNode node : children) {
            RecordContext rc = get(node.name, node.keys);
            if (rc.getParameterAsChar("814") == 'C') {
                records.get(node.name).remove(rc);
            } else {
                rc.setParameter("814", "D");
                rc.setParameter("815", Boolean.TRUE);
            }
            if (!node.isLeaf())
                removeNodes(node.children);
        }
    }

    void next(CommandContext cc) {
        if (isEditable) {
            Set<String> set = new TreeSet<String>(records.keySet());
            int size = set.size();
            String[] keys = new String[size];
            set.toArray(keys);
            for (int i = size - 1; i >= 0; i--) {
                List<RecordContext> rcs = records.get(keys[i]);
                if (!rcs.isEmpty()) {
                    RecordContext rc = rcs.get(0);
                    if (!Strings.isBlank(rc.getParameterAsString("837"))) {
                        CompliableScriptEngine compiler = null;
                        EventLogic el = RuntimeUtils.castTo(cc, rc,
                                rc.getParameterAsString("837"));
                        if (el != null)
                            compiler = ScriptEngineFactory
                                    .createCompilableEngine(cc, el.getContent());
                        if (compiler != null) {
                            try {
                                for (RecordContext ctx : rcs) {
                                    if (ctx.getParameterAsBoolean("815")
                                            && ctx.getParameterAsChar("814") != 'D')
                                        compiler.eval(ctx);
                                }
                            } catch (ScriptEvalExitException ex) {
                                throw new ScriptRuntimeException(
                                        ex.getMessage(), cc.getMessage(
                                        ex.getMessage(), ex.getRecord()));
                            } finally {
                                compiler.destroy();
                            }
                        }// if
                    }// if
                }// if
            }// for
            keys = null;
            set = null;
        }// if
    }

    void save(CommandContext cc) {
        if (isEditable) {
            for (List<RecordContext> rcs : records.values()) {
                if (!rcs.isEmpty()) {
                    TableBean tb = cc.getEntity(rcs.get(0)
                            .getParameterAsString("833"));
                    BizDao dao = cc.getBizDao(tb.getSubType());
                    for (RecordContext rc : rcs) {
                        if (rc.getParameterAsBoolean("815")
                                && rc.getParameterAsChar("814") != 'R') {
                            char code = rc.getParameterAsChar("814");
                            rc.record.setKey(tb.getKey());
                            if (code == 'C') {
                                dao.insert(cc.getUser(), tb,
                                        rc.record, false);
                            } else if (code == 'U')
                                dao.update(cc.getUser(), tb.getId(),
                                        rc.record, false);
                            else if (code == 'D') {
                                List<FieldDo> key = new ArrayList<FieldDo>(tb
                                        .getKey().size());
                                for (String name : tb.getKey()) {
                                    FieldDo fd = rc.record.get(name);
                                    if (fd == null)
                                        throw new FieldNotFoundException(
                                                "entity.table.field.not.found",
                                                tb.getId(), name);
                                    key.add(fd);
                                }
                                dao.delete(cc.getUser(), tb.getId(),
                                        key, false);
                            }
                        }
                    }
                }
            }
        }
    }

    void reset() {
        if (isEditable) {
            for (List<RecordContext> rcs : records.values()) {
                for (Iterator<RecordContext> itr = rcs.iterator(); itr
                        .hasNext(); ) {
                    RecordContext rc = itr.next();
                    if (rc.getParameterAsBoolean("815")
                            && rc.getParameterAsChar("814") != 'R') {
                        char code = rc.getParameterAsChar("814");
                        if (code == 'C') {
                            rc.setParameter("814", "U");
                            rc.setParameter("815", false);
                        } else if (code == 'D')
                            itr.remove();
                    }
                }
            }
        }
    }

    void applyAll(String name, Object value) {
        for (List<RecordContext> rcs : records.values()) {
            for (Iterator<RecordContext> itr = rcs.iterator(); itr.hasNext(); ) {
                RecordContext rc = itr.next();
                rc.setParameter(name, value);
            }
        }
    }

    public String toText() {
        List<RecordContext> tmp = records.get("");
        if (tmp == null || tmp.isEmpty())
            return "";
        StringBuilder sb = new StringBuilder();
        createText(sb, root);
        return sb.toString();
    }

    private void createText(StringBuilder sb, RecordNode node) {
        RecordContext rc = get(node.name, node.keys);
        Record record = rc.getData();
        FieldDo[] fields = Lang.collection2array(record.getData());
        for (int i = 0; i < fields.length; i++) {
            FieldDo field = fields[i];
            if (field.getDescription().endsWith("#")) {
                if (!node.isLeaf()) {
                    for (RecordNode n : node.children) {
                        if (n.name.equals(field.getDescription()))
                            createText(sb, n);
                    }
                }
            } else if (field.getDescription().equals("{4:")
                    || field.getDescription().equals("-}")) {
                sb.append(field.getDescription()).append("\r\n");
            } else if (field.getDescription().startsWith("{")
                    && field.getDescription().endsWith(":")) {
                if (field.getValue() != null && !field.getValue().equals("")) {
                    sb.append(field.getDescription());
                    sb.append(field.getValue());
                    sb.append("}");
                    sb.append("\r\n");
                }
            } else if (isTag(field.getDescription())) {
                if (field.getValue() != null && !field.getValue().equals("")) {
                    sb.append(":");
                    sb.append(field.getDescription());
                    sb.append(":");
                    sb.append(field.getValue());
                    sb.append("\r\n");
                } else if (field.getDescription().startsWith("15")
                        && Character.isUpperCase(field.getDescription().charAt(
                        2))) {// 15A~15I
                    boolean findContent = false;
                    for (int j = i + 1; j < fields.length; j++) {
                        FieldDo fd = fields[j];
                        if (isTag(fd.getDescription()) && fd.getValue() != null
                                && !fd.getValue().equals("")) {
                            findContent = true;
                            break;
                        }
                    }
                    if (findContent) {
                        sb.append(":");
                        sb.append(field.getDescription());
                        sb.append(":").append("\r\n");
                    }
                }
            }
        }
    }

    private boolean isTag(String name) {
        char[] cs = name.toCharArray();
        if (cs.length < 4) {
            for (int i = 0; i < cs.length; i++) {
                if (Strings.isChineseCharacter(cs[i]))
                    return false;
            }
            return true;
        } else
            return false;
    }

    /**
     * @return the table
     */
    public TableBean getTable() {
        return table;
    }

    private class RecordNode implements Serializable {

        private static final long serialVersionUID = 1L;

        private String name;

        private Object[] keys;

        private RecordNode parent;

        private List<RecordNode> children;

        RecordNode(String name, Object[] keys) {
            this.name = name;
            this.keys = keys;
        }

        void appendChild(RecordNode child) {
            child.parent = this;
            if (children == null)
                children = new ArrayList<RecordNode>();
            children.add(child);
        }

        void removeChild(RecordNode child) {
            if (children != null)
                children.remove(child);
        }

        boolean isLeaf() {
            return children == null || children.isEmpty();
        }
    }
}
